#include <algorithm>
#include <iostream>
#include <string>
#include "plc2llvm/Visitor/strategy/statement/stmt.h"
#include "plc2llvm/utils/Log.h"
#include "plc2llvm/utils/Utils.h"
#include "plc2llvm/Semantic/SemanticError.h"
#include "plc2llvm/Semantic/SemanticWarning.h"
#include "plc2llvm/ScopeSystem/ScopeManager.h"
#include "plc2llvm/TypeSystem/TypeMachine.h"
#include "llvm/ADT/Twine.h"

#define ANY_CAST_TO_OBJ(x) std::any_cast<std::shared_ptr<plcst::Object>>(x)
#define ANY_CAST_TO_TYPE(x) std::any_cast<std::shared_ptr<plcst::Type>>(x)

namespace stmt
{
    std::any visitStmt_List(plcst::PLCSTParser::Stmt_ListContext *ctx, Visitor *visitor)
    {

        for (auto *stmt : ctx->stmt())
        {
            visitor->visit(stmt);
        }
        return std::any();
    }

    std::any visitStmt(plcst::PLCSTParser::StmtContext *ctx, Visitor *visitor)
    {
        return visitor->visitChildren(ctx);
    }

    std::any visitIteration_Stmt(plcst::PLCSTParser::Iteration_StmtContext *ctx, Visitor *visitor)
    {
        if (ctx->while_Stmt() != nullptr | ctx->for_Stmt() != nullptr | ctx->repeat_Stmt() != nullptr)
        {
            return visitor->visitChildren(ctx);
        }
        return std::any();
    }

    std::any visitFor_Stmt(plcst::PLCSTParser::For_StmtContext *ctx, Visitor *visitor)
    {
        // codegen
        auto func_obj = plcst::ScopeManager::getScopeManager().getCurrentScope()->getSrcObject();
        auto raw_llvm_func = func_obj->llvmval;
        auto llvm_func = llvm::cast<llvm::Function>(raw_llvm_func);
        // create basicblocks
        auto for_entry = llvm::BasicBlock::Create(visitor->context, "for.entry", llvm_func);
        auto for_cond = llvm::BasicBlock::Create(visitor->context, "for.cond", llvm_func);
        auto for_body = llvm::BasicBlock::Create(visitor->context, "for.body", llvm_func);
        auto for_inc = llvm::BasicBlock::Create(visitor->context, "for.inc", llvm_func);
        auto for_end = llvm::BasicBlock::Create(visitor->context, "for.end", llvm_func);
        //-----------------------------------------------------------------------------------------------------------------

        visitor->builder->CreateBr(for_entry);
        /*
            entry:
                ctrl_var := for_list[0];
                br for_cond;
        */
        // get control variable obj
        visitor->builder->SetInsertPoint(for_entry);
        auto objectname = ctx->control_Variable()->getText();
        std::shared_ptr<plcst::Object> ctrl_obj = plcst::ScopeManager::getScopeManager().find<plcst::Object>(objectname);
        // type check: ctrl-obj should be a number
        if(!plcst::TypeMachine::getTypeMachine().isNumber(ctrl_obj->getType()->getTypeKind())){
            throw SemanticError(visitor->getTokenStream(), ctx->control_Variable(), "control variable should be a number!");
        }
        auto ctrl_value_ptr = ctrl_obj->llvmval;
        // get for list: (obj, obj ,[obj])
        auto raw_forlist = visitor->visit(ctx->for_List());
        auto for_list = std::any_cast<std::vector<std::shared_ptr<plcst::Object>>>(raw_forlist);
        auto for_list_value_0 = for_list[0];
        // for_list[0] cast to type of ctrl_obj
        auto castedValue = for_list_value_0->getType()->castTo(ctrl_obj->getType(), for_list_value_0->llvmval, visitor->builder);
        auto counter_tmp1 = visitor->builder->CreateStore(for_list_value_0->llvmval, ctrl_value_ptr);
        visitor->builder->CreateBr(for_cond);

        /*
            for cond:
                ctrl_var > for_list[1]?
                    no: br for_body
                    yes: br for_end
        */
        visitor->builder->SetInsertPoint(for_cond);
        auto counter_tmp2 = visitor->builder->CreateLoad(ctrl_obj->getType()->llvmty, ctrl_value_ptr);
        auto for_list_value_1 = for_list[1];
        auto castedValue1 = for_list_value_1->getType()->castTo(ctrl_obj->getType(), for_list_value_1->llvmval, visitor->builder);
        auto cmp = visitor->builder->CreateICmpSLE(counter_tmp2, castedValue1, "cmp");
        visitor->builder->CreateCondBr(cmp, for_body, for_end);
        //-----------------------------------------------------------------------------------------------------------------


        /*
            for body:
                ....(child ctx: stmt list)
                br for inc
        */
        visitor->builder->SetInsertPoint(for_body);
        visitor->visit(ctx->stmt_List());
        visitor->builder->CreateBr(for_inc);

        /*
            for inc:
                ctrl_value += for_list[2]
                br for cond
        */
        visitor->builder->SetInsertPoint(for_inc);
        auto ctrl_value_tmp3 = visitor->builder->CreateLoad(ctrl_obj->getType()->llvmty, ctrl_value_ptr);
        llvm::Value* inc;
        if(for_list.size() == 3){
            auto for_list_value_2 = for_list[2];
            inc = for_list_value_2->getType()->castTo(ctrl_obj->getType(), for_list_value_2->llvmval, visitor->builder);
        }else{
            auto intTy = plcst::ScopeManager::getScopeManager().getGlobalScope()->find<plcst::Type>("INT");
            inc = intTy->castTo(ctrl_obj->getType(), llvm::ConstantInt::get(intTy->llvmty, 1), visitor->builder);
        }
        auto new_ctrl_value = visitor->builder->CreateAdd(ctrl_value_tmp3, inc, "inc");
        visitor->builder->CreateStore(new_ctrl_value, ctrl_value_ptr);
        visitor->builder->CreateBr(for_cond);

        /*
            for end:
                ret void
        */
        visitor->builder->SetInsertPoint(for_end);

        return nullptr;
    }

    std::any visitAssign_Stmt(plcst::PLCSTParser::Assign_StmtContext *ctx, Visitor *visitor)
    {
        return visitor->visitChildren(ctx);
    }

    std::any visitFor_List(plcst::PLCSTParser::For_ListContext *ctx, Visitor *visitor)
    {
        // visit all expression and append to obj list
        std::vector<std::shared_ptr<plcst::Object>> obj_list;
        for(auto expr : ctx->expression()){
            auto raw_expr = visitor->visit(expr);
            auto expr_obj = ANY_CAST_TO_OBJ(raw_expr);
            obj_list.push_back(expr_obj);
            // type check: expr should be a number
            auto expr_type = expr_obj->getType();
            if(!plcst::TypeMachine::getTypeMachine().isNumber(expr_type->getTypeKind())){
                throw SemanticError(visitor->getTokenStream(), expr, "not a number!");
            }
        }
        return obj_list;
    }

    std::any visitVar_Assign(plcst::PLCSTParser::Var_AssignContext *ctx, Visitor *visitor)
    {
        auto raw_var = visitor->visit(ctx->variable());
        auto var = ANY_CAST_TO_OBJ(raw_var);
        auto raw_expr = visitor->visit(ctx->expression());
        auto expr = ANY_CAST_TO_OBJ(raw_expr);
        auto expressiontype = expr->getType();
        // 类型检查
        auto vartype = var->getType();
        auto msg = expressiontype->canConvertTo(vartype);
        switch (msg)
        {
        case plcst::ConvertionSignal::IMPLICIT_CONVERSION:
            break;
        case plcst::ConvertionSignal::EXPLICIT_CONVERSION:
            // 给出一个需要显式转换的错误
            throw SemanticError(visitor->getTokenStream(), ctx, "Explicit type conversion is required");
            break;
        case plcst::ConvertionSignal::ERROR_CONVERSION:
            // 报一个类型不合理，无法赋值的错误
            throw SemanticError(visitor->getTokenStream(), ctx, "Illegal type, cannot be assigned");
            break;
        }

        // codegen
        auto castedExpr = expressiontype->castTo(vartype, expr->llvmval, visitor->builder);
        visitor->builder->CreateStore(castedExpr, var->llvmval);
        //-------------------------------------------------------------------------------------------

        return std::any();
    }

    std::any visitVariable(plcst::PLCSTParser::VariableContext *ctx, Visitor *visitor)
    {
        // directly presented variable
        if (ctx->symbolic_Variable() == nullptr)
        {
            return ctx->Direct_Variable()->getText();
        }
        else // other variable
        {
            auto objectname = ctx->symbolic_Variable()->getText();
            std::shared_ptr<plcst::Object> raw_objectSymbol = plcst::ScopeManager::getScopeManager().find<plcst::Object>(objectname);
            return raw_objectSymbol;
        }
    }

    std::any visitSymbolic_Variable(plcst::PLCSTParser::Symbolic_VariableContext *ctx, Visitor *visitor)
    {
        return visitor->visitChildren(ctx);
    }

    std::any visitVar_Access(plcst::PLCSTParser::Var_AccessContext *ctx, Visitor *visitor)
    {
        return visitor->visitChildren(ctx);
    }
}